#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "xdbase.h"
#include "logger.h"
#include "mutex.h"
#include "mmtrie.h"
#include "xmm.h"
#include "db.h"
/* mkdir as path*/
int xdbase_mkdir(char *path)
{
    struct stat st;
    char fullpath[DB_PATH_MAX];
    char *p = NULL;
    int level = -1, ret = -1;

    if(path)
    {
        strcpy(fullpath, path);
        p = fullpath;
        while(*p != '\0')
        {
            if(*p == '/' )
            {
                level++;
                while(*p != '\0' && *p == '/' && *(p+1) == '/')++p;
                if(level > 0)
                {
                    *p = '\0';                    memset(&st, 0, sizeof(struct stat));
                    ret = stat(fullpath, &st);
                    if(ret == 0 && !S_ISDIR(st.st_mode)) return -1;
                    if(ret != 0 && mkdir(fullpath, 0755) != 0) return -1;
                    *p = '/';
                }
            }
            ++p;
        }
        return 0;
    }
    return -1;
}

/* initialize XDB */
XDBASE *xdbase_init(char *basedir)
{
    char path[XDB_PATH_MAX];
    struct stat st = {0};
    XDBASE *xdbase = NULL;
    int i = 0;

    if(basedir && (xdbase = (XDBASE *)xmm_new(sizeof(XDBASE))))
    {
        /* logger */
        sprintf(path, "%s/%s", basedir, "xdb.log");        
        xdbase_mkdir(path);
        LOGGER_INIT(xdbase->logger, path);
        sprintf(path, "%s/%s", basedir, "xdb.map");
        xdbase->map = mmtrie_init(path);
        sprintf(path, "%s/%s", basedir, "xdb.state");
        if((xdbase->stateio.fd = open(path, O_CREAT|O_RDWR, 0644)) > 0
                && fstat(xdbase->stateio.fd, &st) == 0)
        {
            xdbase->stateio.end = st.st_size;
            if(st.st_size == 0)
            {
                xdbase->stateio.end = xdbase->stateio.size = sizeof(XDBSTATE);
                if(ftruncate(xdbase->stateio.fd, xdbase->stateio.end) != 0)
                {
                    FATAL_LOGGER(xdbase->logger, "ftruncate state %s failed, %s", path, strerror(errno));
                    _exit(-1);
                }
            }
            if((xdbase->stateio.map = mmap(NULL, xdbase->stateio.end, PROT_READ|PROT_WRITE,
                            MAP_SHARED, xdbase->stateio.fd, 0)) == NULL 
                    || xdbase->stateio.map == (void *)-1)
            {
                FATAL_LOGGER(xdbase->logger, "mmap state:%s failed, %s", path, strerror(errno));
                _exit(-1);
            }
            xdbase->state = (XDBSTATE *)(xdbase->stateio.map);
        }
        else
        {
            fprintf(stderr, "open state file:%s failed, %s", path, strerror(errno));
            _exit(-1);
        }
        MUTEX_INIT(db->mutex); 
        for(i = 0; i < XDB_DISK_MAX; i++)
        {
            if(xdbase->state->xdisks[i].status > 0)
            {
                xdbase->state->xdisks[i].db = (void *)db_init(xdbase->state->xdisks[i].disk, 0);
            }
            else
                xdbase->state->xdisks[i].db = NULL;
        }
    }
    return xdbase;
}

/* add disk */
int xdbase_add_disk(XDBASE *xdbase, char *disk, off_t limit, int port)
{
    int diskid = -1, x = 0, n = 0;

    if(xdbase && disk && xdbase->state && xdbase->state->nxdisks < XDB_DISK_MAX)
    {
        MUTEX_LOCK(xdbase->mutex);
        n = strlen(disk);
        if(mmtrie_get(MMTR(xdbase->map), disk, n) <= 0)
        {
            while(x < XDB_DISK_MAX && xdbase->state->xdisks[x].status)++x; 
            mmtrie_add(MMTR(xdbase->map), disk, n, x+1);
            strcpy(xdbase->state->xdisks[x].disk, disk);
            xdbase->state->xdisks[x].status = 1;
            xdbase->state->xdisks[x].limit = limit;
            xdbase->state->xdisks[x].port = port;
            xdbase->state->nxdisks++;
            xdbase->state->xdisks[x].db = db_init(disk, 0);
            diskid = x;
        }
        else diskid = -1;
        MUTEX_UNLOCK(xdbase->mutex);
    }
    return diskid;
}

/* update disk limit */
int xdbase_update_disk(XDBASE *xdbase, int diskid, off_t limit)
{
    int ret = -1;

    if(xdbase && diskid >= 0 && diskid < XDB_DISK_MAX)
    {
        MUTEX_LOCK(xdbase->mutex);
        if(xdbase->state && xdbase->state->nxdisks > 0 
                && xdbase->state->xdisks[diskid].status)
        {
            xdbase->state->xdisks[diskid].limit = limit;
            ret = 0;
        }
        MUTEX_UNLOCK(xdbase->mutex);
    }
    return ret;
}

/* del disk */
int xdbase_del_disk(XDBASE *xdbase, int diskid)
{
    int ret = -1;

    if(xdbase && diskid >= 0 && diskid < XDB_DISK_MAX)
    {
        MUTEX_LOCK(xdbase->mutex);
        if(xdbase->state && xdbase->state->nxdisks > 0 
                && xdbase->state->xdisks[diskid].status)
        {
            xdbase->state->xdisks[diskid].status = 0;
            xdbase->state->xdisks[diskid].limit = 0;
            xdbase->state->xdisks[diskid].port = 0;
            xdbase->state->xdisks[diskid].disk[0] = 0;
            ret = 0;
        }
        MUTEX_UNLOCK(xdbase->mutex);
    }
    return ret;
}

/* list disks */
int xdbase_list_disk(XDBASE *xdbase, char *out)
{
    struct statfs fs = {0};
    int ret = -1, i = 0;
    char *p = NULL;

    if(xdbase && (p = out) && xdbase->state->nxdisks > 0)
    {
        MUTEX_LOCK(xdbase->mutex);
        p += sprintf(p, "({'disklist':{");
        for(i = 0; i < XDB_DISK_MAX; i++) 
        {
            if(xdbase->state->xdisks[i].status 
                    && statfs(xdbase->state->xdisks[i].disk, &fs) == 0)
            {
                p += sprintf(p, "'%d':{'sport':'%d', 'name':'%s', 'bsize':'%ld', 'iosize':%ld', 'total':'%ld', 'free':'%ld'},", i, xdbase->state->xdisks[i].port, xdbase->state->xdisks[i].disk, (long)fs.f_bsize, (long)fs.f_iosize, (long)fs.f_blocks, (long)fs.f_bfree);
            }
        }
        --p;
        p += sprintf(p, "}})");
        ret = p - out;
        MUTEX_UNLOCK(xdbase->mutex);
    }
    return ret;
}

/* add data */
int xdbase_add_data(XDBASE *xdbase, int diskid, int64_t id, char *data, int ndata);
/* update data */
int xdbase_set_data(XDBASE *xdbase, int diskid, int64_t id, char *data, int ndata);
/* del data */
int xdbase_del_data(XDBASE *xdbase, int diskid, int64_t id);
/* get data */
int xdbase_get_data(XDBASE *xdbase, int diskid, int64_t id, char *data);
/* close XDB */
void xdbase_close(XDBASE *xdbase)
{
    int i = 0;

    if(xdbase)
    {
        mmtrie_clean(MMTR(xdbase->map));
        MUTEX_DESTROY(xdbase->mutex);
        LOGGER_CLEAN(xdbase->logger);
        if(xdbase->state)
        {
            for(i = 0; i < XDB_DISK_MAX; i++)
            {
                if(xdbase->state->xdisks[i].db)
                {
                    db_clean(PDB(xdbase->state->xdisks[i].db));
                }
            }
        }
        if(xdbase->stateio.map)
        {
            munmap(xdbase->stateio.map, sizeof(XDBSTATE));
            xdbase->stateio.map = NULL;
        }
        if(xdbase->stateio.fd > 0)
        {
            close(xdbase->stateio.fd);
            xdbase->stateio.fd = -1;
        }
        xmm_free(xdbase, sizeof(XDBASE));
    }
    return ;
}

#ifdef _DEBUG_XDBASE
int main()
{
    XDBASE *xdb = NULL;
    char buf[65536];
    int n = 0, id = 0;

    if((xdb = xdbase_init("/tmp/xdbase")))
    {
        //add disk 
        id = xdbase_add_disk(xdb, "/tmp/db1", 1024 * 1024 * 1024, 6001);
        id = xdbase_add_disk(xdb, "/tmp/db2", 1024 * 1024 * 1024, 6002);
        id = xdbase_add_disk(xdb, "/tmp/db3", 1024 * 1024 * 1024, 6003);
        id = xdbase_add_disk(xdb, "/tmp/db4", 1024 * 1024 * 1024, 6004);
        if((n = xdbase_list_disk(xdb, buf)) > 0) fprintf(stdout, "list:%s\n", buf);
        xdbase_del_disk(xdb, id);
        if((n = xdbase_list_disk(xdb, buf)) > 0) fprintf(stdout, "list:%s\n", buf);
        xdbase_close(xdb);
    }
}
//gcc -o xdb xdbase.c utils/xmm.c utils/mmtrie.c utils/db.c -I utils -D_DEBUG_XDBASE && ./xdb
#endif
